"""
适配器抽象基类

本模块定义了市场数据服务所有数据源适配器必须实现的标准接口。
通过这些抽象基类，我们实现了数据源的插件化和可扩展性。

核心接口：
1. HistoricalDataAdapter - 历史数据适配器接口
2. RealtimeDataAdapter - 实时数据适配器接口
3. HybridDataAdapter - 混合适配器接口（同时支持历史和实时）

设计原则：
- 接口隔离：不同类型的适配器实现不同的接口
- 标准化：所有适配器返回统一的数据模型
- 异步优先：所有I/O操作都是异步的
- 类型安全：使用类型注解确保类型安全

作者: 架构组
创建日期: 2025-10-04
"""

from abc import ABC, abstractmethod
from typing import List, Optional, Callable, Awaitable, AsyncIterator, Dict, Any
from datetime import datetime
from decimal import Decimal

# 导入标准数据模型（注意：models.py需要先创建）
from .models import Bar, Tick, Orderbook


class DataAdapter(ABC):
    """
    所有适配器的基类
    
    定义所有适配器共有的生命周期方法和通用功能
    """
    
    @abstractmethod
    async def connect(self) -> None:
        """
        建立与数据源的连接
        
        职责：
        - 初始化连接对象（HTTP客户端、WebSocket、TCP连接等）
        - 进行身份验证
        - 设置连接参数
        - 验证连接可用性
        
        Raises:
            AdapterConnectionError: 连接失败时抛出
        
        示例：
            >>> adapter = TushareAdapter(token="xxx")
            >>> await adapter.connect()
        """
        pass
    
    @abstractmethod
    async def disconnect(self) -> None:
        """
        断开与数据源的连接
        
        职责：
        - 关闭连接对象
        - 释放资源（文件句柄、网络连接等）
        - 清理缓存
        - 保存必要的状态
        
        注意：
        - 此方法应该是幂等的，多次调用不应产生副作用
        - 即使发生异常，也应尽可能完成资源清理
        
        示例：
            >>> await adapter.disconnect()
        """
        pass
    
    @abstractmethod
    async def health_check(self) -> bool:
        """
        健康检查
        
        职责：
        - 验证连接是否有效
        - 测试数据源是否可访问
        - 检查认证是否过期
        
        Returns:
            bool: True表示健康，False表示不健康
        
        注意：
        - 此方法不应抛出异常，而应返回False
        - 健康检查应该是轻量级的，避免耗时操作
        
        示例：
            >>> is_healthy = await adapter.health_check()
            >>> if not is_healthy:
            >>>     await adapter.reconnect()
        """
        pass
    
    async def __aenter__(self):
        """上下文管理器：进入"""
        await self.connect()
        return self
    
    async def __aexit__(self, exc_type, exc_val, exc_tb):
        """上下文管理器：退出"""
        await self.disconnect()


class HistoricalDataAdapter(DataAdapter):
    """
    历史数据适配器接口
    
    用于获取历史K线、历史Tick等历史数据。
    适用于不支持实时推送的数据源，如Tushare、Yahoo Finance等。
    
    特点：
    - 基于请求-响应模式
    - 支持批量查询
    - 支持时间范围过滤
    - 支持多种数据频率
    
    示例适配器：
    - TushareAdapter：Tushare金融数据接口
    - YahooFinanceAdapter：Yahoo Finance API
    - AkShareAdapter：AkShare数据接口
    """
    
    @abstractmethod
    async def query_bars(
        self,
        symbol: str,
        start_time: datetime,
        end_time: datetime,
        frequency: str = "1d",
        **kwargs
    ) -> List[Bar]:
        """
        查询K线数据（蜡烛图数据）
        
        这是历史数据适配器的核心方法，用于获取指定时间范围的K线数据。
        
        Args:
            symbol: 交易标的代码，必须使用标准格式
                   - 股票：'000001.SZ'（深圳）、'600000.SH'（上海）
                   - 期货：'rb2310.SHFE'（合约代码.交易所）
                   - 加密货币：'BTC-USD'、'ETH-USDT'
            start_time: 开始时间（包含），datetime对象
            end_time: 结束时间（包含），datetime对象
            frequency: 数据频率，默认为'1d'（日线）
                      支持的频率：
                      - 分钟级：'1m', '5m', '15m', '30m', '60m'
                      - 小时级：'1h', '4h'
                      - 日级：'1d'
                      - 周级：'1w'
                      - 月级：'1M'
            **kwargs: 额外的适配器特定参数
        
        Returns:
            List[Bar]: Bar对象列表，按时间升序排列
                      如果没有数据，返回空列表（而不是None）
        
        Raises:
            AdapterConnectionError: 连接失败
            AdapterDataError: 数据格式错误
            AdapterTimeoutError: 请求超时
            ValueError: 参数不合法（如时间范围无效）
        
        注意事项：
        1. 返回的数据应该已经去重
        2. 时间戳应该使用UTC或明确指定时区
        3. 价格和成交量应该使用Decimal类型保证精度
        4. 如果数据源不支持某个频率，应该抛出NotImplementedError
        
        示例：
            >>> bars = await adapter.query_bars(
            ...     symbol='000001.SZ',
            ...     start_time=datetime(2025, 1, 1),
            ...     end_time=datetime(2025, 10, 4),
            ...     frequency='1d'
            ... )
            >>> print(f"获取了 {len(bars)} 条K线数据")
            >>> print(f"第一条: {bars[0].timestamp} OHLC: {bars[0].open}/{bars[0].high}/{bars[0].low}/{bars[0].close}")
        """
        pass
    
    async def query_ticks(
        self,
        symbol: str,
        start_time: datetime,
        end_time: datetime,
        **kwargs
    ) -> List[Tick]:
        """
        查询逐笔成交数据（可选实现）
        
        某些数据源可能提供历史Tick数据（如部分付费数据源）。
        如果数据源不支持，可以不实现此方法。
        
        Args:
            symbol: 交易标的代码
            start_time: 开始时间
            end_time: 结束时间
            **kwargs: 额外参数
        
        Returns:
            List[Tick]: Tick对象列表
        
        Raises:
            NotImplementedError: 数据源不支持时抛出
        """
        raise NotImplementedError(f"{self.__class__.__name__} does not support historical tick data")
    
    async def query_bars_batch(
        self,
        symbols: List[str],
        start_time: datetime,
        end_time: datetime,
        frequency: str = "1d",
        **kwargs
    ) -> Dict[str, List[Bar]]:
        """
        批量查询多个标的的K线数据（可选实现）
        
        默认实现是循环调用query_bars，适配器可以重写此方法以优化性能。
        
        Args:
            symbols: 交易标的代码列表
            start_time: 开始时间
            end_time: 结束时间
            frequency: 数据频率
            **kwargs: 额外参数
        
        Returns:
            Dict[str, List[Bar]]: 字典，key为symbol，value为Bar列表
        
        示例：
            >>> result = await adapter.query_bars_batch(
            ...     symbols=['000001.SZ', '600000.SH'],
            ...     start_time=datetime(2025, 1, 1),
            ...     end_time=datetime(2025, 10, 4)
            ... )
            >>> for symbol, bars in result.items():
            ...     print(f"{symbol}: {len(bars)} bars")
        """
        import asyncio
        
        # 默认实现：并发查询
        tasks = [
            self.query_bars(symbol, start_time, end_time, frequency, **kwargs)
            for symbol in symbols
        ]
        results = await asyncio.gather(*tasks, return_exceptions=True)
        
        # 构建结果字典，过滤掉异常
        return {
            symbol: result
            for symbol, result in zip(symbols, results)
            if not isinstance(result, Exception)
        }


class RealtimeDataAdapter(DataAdapter):
    """
    实时数据适配器接口
    
    用于订阅实时Tick、实时订单簿等实时数据流。
    适用于支持推送的数据源，如WebSocket行情源、CTP期货等。
    
    特点：
    - 基于订阅-推送模式
    - 使用回调函数接收数据
    - 支持多标的同时订阅
    - 需要管理订阅状态
    
    示例适配器：
    - CTPAdapter：CTP期货实时行情
    - WebSocketTickAdapter：WebSocket行情源
    - BinanceStreamAdapter：币安实时行情流
    """
    
    @abstractmethod
    async def subscribe_tick(
        self,
        symbols: List[str],
        callback: Callable[[Tick], Awaitable[None]]
    ) -> None:
        """
        订阅实时Tick数据
        
        Args:
            symbols: 要订阅的标的代码列表
            callback: 回调函数，当有新Tick数据到达时被调用
                     签名：async def callback(tick: Tick) -> None
                     注意：回调函数必须是异步函数
        
        Raises:
            AdapterConnectionError: 连接未建立或订阅失败
            ValueError: symbols参数为空或格式不正确
        
        注意事项：
        1. 此方法应该是非阻塞的，订阅完成后立即返回
        2. 回调函数会在独立的任务中执行，不应阻塞主流程
        3. 同一个symbol可以被多次订阅（支持多个回调）
        4. 回调函数中的异常应该被捕获，不应影响其他订阅
        
        示例：
            >>> async def on_tick_received(tick: Tick):
            ...     print(f"收到Tick: {tick.symbol} 价格: {tick.price}")
            ...     await save_to_database(tick)
            >>> 
            >>> await adapter.subscribe_tick(
            ...     symbols=['rb2310.SHFE', 'au2312.SHFE'],
            ...     callback=on_tick_received
            ... )
            >>> # 之后，每当有新的Tick数据，on_tick_received会被自动调用
        """
        pass
    
    @abstractmethod
    async def unsubscribe_tick(self, symbols: List[str]) -> None:
        """
        取消订阅Tick数据
        
        Args:
            symbols: 要取消订阅的标的代码列表
        
        注意事项：
        1. 如果symbol未被订阅，应该静默处理（不抛异常）
        2. 取消订阅后，不应再收到该symbol的数据
        3. 如果同一symbol有多个回调，取消订阅应该移除所有回调
        
        示例：
            >>> await adapter.unsubscribe_tick(['rb2310.SHFE'])
        """
        pass
    
    @abstractmethod
    async def subscribe_orderbook(
        self,
        symbols: List[str],
        callback: Callable[[Orderbook], Awaitable[None]],
        depth: int = 5
    ) -> None:
        """
        订阅实时订单簿（深度行情）
        
        Args:
            symbols: 要订阅的标的代码列表
            callback: 回调函数，当订单簿更新时被调用
                     签名：async def callback(orderbook: Orderbook) -> None
            depth: 订单簿深度（档位数量），默认5档
                  - 5: 5档买卖盘
                  - 10: 10档买卖盘
                  - 20: 20档买卖盘
        
        Raises:
            AdapterConnectionError: 连接未建立或订阅失败
            NotImplementedError: 数据源不支持订单簿
        
        注意事项：
        1. 订单簿数据更新频率可能很高，回调函数应该高效
        2. 某些数据源可能不支持订单簿，应抛出NotImplementedError
        3. depth参数取决于数据源支持的档位
        
        示例：
            >>> async def on_orderbook_update(orderbook: Orderbook):
            ...     best_bid = orderbook.bids[0]  # (price, volume)
            ...     best_ask = orderbook.asks[0]
            ...     spread = best_ask[0] - best_bid[0]
            ...     print(f"买卖价差: {spread}")
            >>> 
            >>> await adapter.subscribe_orderbook(
            ...     symbols=['BTC-USD'],
            ...     callback=on_orderbook_update,
            ...     depth=5
            ... )
        """
        pass
    
    async def unsubscribe_orderbook(self, symbols: List[str]) -> None:
        """
        取消订阅订单簿
        
        Args:
            symbols: 要取消订阅的标的代码列表
        
        默认实现：调用子类应该重写以提供具体实现
        """
        pass
    
    async def get_subscribed_symbols(self) -> List[str]:
        """
        获取当前已订阅的标的列表（可选实现）
        
        Returns:
            List[str]: 已订阅的标的代码列表
        
        示例：
            >>> symbols = await adapter.get_subscribed_symbols()
            >>> print(f"当前订阅: {', '.join(symbols)}")
        """
        return []


class HybridDataAdapter(HistoricalDataAdapter, RealtimeDataAdapter):
    """
    混合适配器接口
    
    同时支持历史数据查询和实时数据订阅。
    适用于功能完整的数据源，如CTP、Interactive Brokers等。
    
    特点：
    - 同时继承HistoricalDataAdapter和RealtimeDataAdapter
    - 可以查询历史数据，也可以订阅实时数据
    - 共享同一个连接，资源利用更高效
    
    示例适配器：
    - CTPAdapter：CTP期货（支持历史查询和实时订阅）
    - IBAdapter：盈透证券（Interactive Brokers）
    - TigerBrokerAdapter：老虎证券
    
    注意：
    - 实现此接口时，需要实现所有父类的抽象方法
    - connect()方法应该初始化历史查询和实时订阅所需的所有资源
    - disconnect()方法应该清理所有资源
    
    示例：
        >>> adapter = CTPAdapter(broker_id="xxx", user_id="xxx")
        >>> await adapter.connect()
        >>> 
        >>> # 查询历史数据
        >>> bars = await adapter.query_bars('rb2310.SHFE', start, end)
        >>> 
        >>> # 订阅实时数据
        >>> await adapter.subscribe_tick(['rb2310.SHFE'], on_tick)
        >>> 
        >>> await adapter.disconnect()
    """
    pass


class StreamingDataAdapter(DataAdapter):
    """
    流式数据适配器接口（可选）
    
    使用AsyncIterator提供流式数据访问，适用于需要持续处理大量数据的场景。
    这是RealtimeDataAdapter的一种替代方式，更符合Python的异步迭代器模式。
    
    示例：
        >>> async for tick in adapter.stream_ticks(['BTC-USD']):
        ...     print(f"收到Tick: {tick.price}")
        ...     if some_condition:
        ...         break  # 可以随时停止迭代
    """
    
    async def stream_ticks(
        self,
        symbols: List[str]
    ) -> AsyncIterator[Tick]:
        """
        流式获取Tick数据
        
        Args:
            symbols: 要订阅的标的代码列表
        
        Yields:
            Tick: Tick对象，持续yield直到迭代结束
        
        示例：
            >>> async for tick in adapter.stream_ticks(['rb2310.SHFE']):
            ...     await process_tick(tick)
        """
        # 默认实现：不支持
        raise NotImplementedError(f"{self.__class__.__name__} does not support streaming")
        # 注意：yield语句使函数成为异步生成器
        yield  # 这里是为了类型检查，实际不会执行到


# ==================== 辅助类型 ====================

AdapterType = DataAdapter | HistoricalDataAdapter | RealtimeDataAdapter | HybridDataAdapter
"""适配器类型别名，用于类型注解"""

